package com.yichat.micro.msghttpservice.core.base;

import com.yichat.micro.msghttpservice.common.Constants;
import com.yichat.micro.msghttpservice.common.result.HttpResult;
import com.yichat.micro.msghttpservice.common.util.StringUtil;
import com.yichat.micro.msghttpservice.common.vo.MsgHttpRequestVo;
import com.yichat.micro.msghttpservice.core.metrics.RequestExecutorMonitor;
import com.yichat.micro.msghttpservice.core.record.service.RecordService;
import com.yichat.micro.msghttpservice.core.retry.RetryManager;
import com.yichat.micro.msghttpservice.core.retry.RetryMsgHttpRequestVo;
import com.yichat.micro.msghttpservice.exception.IllegalParameterException;
import com.yichat.micro.msghttpservice.info.HttpRequestRegular;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.*;

/**
 * Created by chenmohan on 2019/03/29
 *
 * @description http请求基类
 */
@Slf4j
@Component
public abstract class AbstractHttpCaller<RES, RESP> {

    protected static final String GET = "get";

    protected static final String POST = "post";

    protected static final String PUT = "put";

    protected static final String DELETE = "delete";

    protected static final String POST_FORM = "post_form";

    protected static final Long default_timeout = 10000L;

    protected static final Integer default_retry_times = 2;

    protected static final String text_plain = "text/plain;charset=utf-8";
    protected static final String application_json = "application/json;charset=utf-8";
    protected static final String application_javascript = "application/javascript;charset=utf-8";
    protected static final String application_xml = "application/xml;charset=utf-8";
    protected static final String application_octet_stream = "application/octet-stream";
    protected static final String text_html = "text/html;charset=utf-8";

    protected static final int status_ok = Constants.status_ok;

    protected static final int status_timeout = Constants.status_timeout;

    protected static final int status_async_commmit = Constants.stauts_async;

    protected static final ThreadPoolExecutor requestExecutor = AdvancedExecutor.newRequestExecutor();

    /**
     * 默认的回调接口成功时,应该返回的字符串值
     */
    @Value("${msg-http-service.callback.success.value}")
    protected String callbackSuccessResponse;

    @Autowired
    protected RetryManager retryManager;

    @Autowired
    protected ApplicationContext applicationContext;

    @Autowired
    RecordService recordService;

    @Autowired
    RequestExecutorMonitor requestExecutorMonitor;
    /*
    @PostConstruct
    protected void init() {
        Boolean asyncRecord = applicationContext.getEnvironment().getProperty("msg-http-service.async-record.enable", Boolean.class);
        this.recordService = this.applicationContext.getBean(asyncRecord ? "asyncRecordServiceImpl" : "recordServiceImpl", RecordService.class);
        log.info("【STARTING】:: {} record ", asyncRecord ? "ASYNC" : "SYNC");
    }
    */

    public HttpResult call(MsgHttpRequestVo requestParam) {

        checkRequiredParam(requestParam);

        setDefaultValue(requestParam);

        Long sid = genStaticId();

        //将上游服务的请求参数对象包装成1个重试对象,如果请求超时或失败,能方便重试
        RetryMsgHttpRequestVo retryRequestParam = retryManager.wrap(requestParam, sid);

        //有一个有效请求,总处理请求数量+1
        requestExecutorMonitor.addRequestNumber();

        //尝试第一次调用,如果第一次调用失败,那么之后的重试调用交由重试组件处理
        HttpResult t = process(retryRequestParam);

        return t;
    }


    protected HttpRequestRegular regular(MsgHttpRequestVo requestParam) {
        return new HttpRequestRegular(requestParam.getReTry(), requestParam.getTimeout(), requestParam.getCallback());
    }

    /**
     * 检查参数是否为空
     *
     * @param requestParam
     * @return
     */
    private boolean checkRequiredParam(MsgHttpRequestVo requestParam) {
        if (StringUtil.isEmpty(requestParam.getUrl()) && StringUtil.isEmpty(requestParam.getData()) && StringUtil.isEmpty(requestParam.getList())) {
            throw new IllegalParameterException("[url],[<data>,<list>]");
        }
        return true;
    }

    /**
     * 设置默认值
     *
     * @param requestParam
     */
    protected MsgHttpRequestVo setDefaultValue(MsgHttpRequestVo requestParam) {
        //对method作小写处理
        if (StringUtil.isEmpty(requestParam.getMethod())) {
            requestParam.setMethod(POST_FORM.trim());  //默认使用Post表单
        } else {
            requestParam.setMethod(requestParam.getMethod().toLowerCase().trim());
        }
        //默认使用"text/plain"
        if (StringUtil.isEmpty(requestParam.getMediaType())) {
            requestParam.setMediaType(text_plain.trim());
        }
        //默认超时时间为10秒
        if (requestParam.getTimeout() == null) {
            requestParam.setTimeout(default_timeout);
        }
        //默认异步处理请求
        if (requestParam.getSync() == null) {
            requestParam.setSync(Constants.disable);
        }
        //默认重试2次
        if (requestParam.getReTry() == null) {
            requestParam.setReTry(default_retry_times);
        }
        //设置请求远程接口后期望返回的值
        if (requestParam.getReqResult() == null) {
            requestParam.setReqResult(callbackSuccessResponse.trim());
        }
        //设置批量处理某次请求失败后,是否继续后面的请求
        if (requestParam.getFailedContinue() == null) {
            requestParam.setFailedContinue(Constants.enalbe);
        }
        return requestParam;
    }

    /**
     * 构造Request体
     *
     * @param requestParam
     * @return
     */
    abstract protected RES buildRequest(MsgHttpRequestVo requestParam);


    /**
     * 真正执行http调用
     *
     * @param request           请求
     * @param retryRequestParam 重试请求参数(对上有服务的传参对象的包装类)
     * @param sid               请求的全局唯一id
     * @return
     * @throws TimeoutException
     */
    abstract protected HttpResult doCall(RES request, RetryMsgHttpRequestVo retryRequestParam, Long sid) throws TimeoutException;

    /**
     * 检查非超时的请求结果是否符合设定预期(符合则视为请求成功),否则执行重试操作
     *
     * @param res
     * @return
     */
    abstract protected boolean isRequestSuccessful(MsgHttpRequestVo request, HttpResult res);

    /**
     * 超时返回的响应对象
     *
     * @return
     */
    abstract protected HttpResult timeout(RetryMsgHttpRequestVo retryRequestParam);


    /**
     * 构建Http回调请求参数对象
     *
     * @param remoteRequest
     * @return
     */
    protected MsgHttpRequestVo buildCallbackRequestParameter(MsgHttpRequestVo remoteRequest) {
        MsgHttpRequestVo callBackVo = MsgHttpRequestVo.builder()
                .url(remoteRequest.getCallback())
                .mediaType(application_json)
                .method(POST).build();
        return setDefaultValue(callBackVo);
    }

    /**
     * 获取唯一的全局id,该id需要保持自增
     *
     * @return
     */
    protected Long genStaticId() {
        return Math.abs(new Random().nextLong());
    }

    /**
     * 重试Http请求,在第一次请求失败或者超时后重试
     *
     * @param retryRequestParam
     * @return
     */
    public HttpResult process(RetryMsgHttpRequestVo retryRequestParam) {
        MsgHttpRequestVo param = null;
        if (retryRequestParam.getList() != null && retryRequestParam.getList().size() > 0) {
            param = buildChildRequestParam(retryRequestParam);
        } else {
            param = retryRequestParam.getVo();
        }
        long sid = retryRequestParam.getSid();

        //构造请求对象
        RES res = buildRequest(param);

        //尝试第一次调用,如果第一次调用失败,那么之后的重试调用交由重试组件处理
        FutureTask<HttpResult> resultFuture = new FutureTask(() -> {
            HttpResult httpResult = doCall(res, retryRequestParam, sid);
            return httpResult;
        });
        requestExecutor.execute(resultFuture);
        HttpResult httpResult = null;
        try {
            httpResult = resultFuture.get(param.getTimeout(), TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("中断了");
        } catch (ExecutionException e) {
            e.printStackTrace();
            System.err.println("ExecutionException,param = " + param);
        } catch (TimeoutException e) {
            httpResult = timeout(retryRequestParam);
            //  System.err.println("超时了,当前重试次数: " + retryRequestParam.getCurrentRetryTimes());
        } catch (NullPointerException e) {

        }

        if (isRequestSuccessful(param, httpResult)) {
            //请求成功后记录日志
            recordService.recordWithSuccessed(retryRequestParam, httpResult);
            //请求成功后是否有批量处理的请求
            if (retryRequestParam.isBatch()) {
                if (retryRequestParam.next()) {
                    //在请求写日志
                    recordService.recordBeforeResponse(retryRequestParam);
                    //如果本次子请求成功,那么这个线程接着递归处理下一个子请求
                    process(retryRequestParam);
                } else {
                    recordService.recoredWithBatchSuccessed(retryRequestParam);
                }
            }
        } else {
            retryManager.delayAndRetry(retryRequestParam);
        }
        return httpResult;
    }

    /**
     * 根据外层请求参数对象构建子请求对象,设置缺省参数
     *
     * @param retryRequestParam
     * @return
     */
    private MsgHttpRequestVo buildChildRequestParam(RetryMsgHttpRequestVo retryRequestParam) {
        MsgHttpRequestVo childRequestParam = retryRequestParam.getList().get(retryRequestParam.getCurrentListIndex());
        MsgHttpRequestVo reference = retryRequestParam.getVo();
        childRequestParam.setReTry(childRequestParam.getReTry() == null ? reference.getReTry() : childRequestParam.getReTry());
        childRequestParam.setTimeout(childRequestParam.getTimeout() == null ? reference.getTimeout() : childRequestParam.getTimeout());
        if (retryRequestParam.getCurrentListIndex() == 0) {
            childRequestParam.setSync(Constants.disable);   //第1条子请求异步请求
        } else {
            childRequestParam.setSync(Constants.enalbe);    //之后的子请求一律同步请求
        }
        childRequestParam.setMethod(StringUtil.isEmpty(childRequestParam.getMethod()) ? reference.getMethod() : childRequestParam.getMethod());
        childRequestParam.setMediaType(StringUtil.isEmpty(childRequestParam.getMediaType()) ? reference.getMediaType() : childRequestParam.getMediaType());
        childRequestParam.setBuz(reference.getBuz());
        childRequestParam.setReqResult(StringUtil.isEmpty(childRequestParam.getReqResult()) ? reference.getReqResult() : childRequestParam.getReqResult());
        childRequestParam.setFailedContinue(childRequestParam.getFailedContinue() == null ? reference.getFailedContinue() : childRequestParam.getFailedContinue());
        childRequestParam.setUrl(StringUtil.isEmpty(childRequestParam.getUrl()) ? reference.getUrl() : childRequestParam.getUrl());
        return childRequestParam;
    }

    private RESP batchProcess(RetryMsgHttpRequestVo retryVo) {
        return null;
    }

    public static ThreadPoolExecutor getRequestExecutor() {
        return requestExecutor;
    }
}
